package com.tian.service.impl.market;

import com.alibaba.fastjson.JSON;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.tian.common.enums.CouponSendTypeEnum;
import com.tian.common.enums.CouponStatusEnum;
import com.tian.common.enums.CouponUseTypeEnum;
import com.tian.common.exception.BusinessException;
import com.tian.common.util.ChargeMarketRedisKeyPrefix;
import com.tian.common.util.CommonResult;
import com.tian.common.util.CouponCodeUtil;
import com.tian.common.util.StringUtil;
import com.tian.dto.user.ChargeUserLoginResDto;
import com.tian.dto.market.*;
import com.tian.dto.market.coupon.CheckCouponReqDto;
import com.tian.dto.market.coupon.CheckCouponRespDto;
import com.tian.dto.market.coupon.CouponCacheDto;
import com.tian.dto.market.coupon.UserCouponCacheDto;
import com.tian.entity.ChargeActivity;
import com.tian.entity.ChargeActivityCondition;
import com.tian.entity.ChargeCoupon;
import com.tian.mapper.ChargeCouponMapper;
import com.tian.mapper.ExchangeCodeMapper;
import com.tian.service.market.ChargeCouponService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RBucket;
import org.redisson.api.RList;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/**
 * {@code @description:} 优惠券实现类
 *
 * @author tianwc 公众号：Java后端技术全栈
 * 在线刷题 1200+java面试题和1000+篇技术文章：<a href="https://woaijava.cc/">博客地址</a>
 * {@code @date:} 2024-08-05 22:50
 * {@code @version:} 1.0
 */
@Slf4j
@Service
public class ChargeCouponServiceImpl implements ChargeCouponService {

    @Resource
    private ChargeCouponMapper chargeCouponMapper;
    @Resource
    private ExchangeCodeMapper exchangeCodeMapper;
    @Resource
    private RedissonClient redissonClient;
    @Resource
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @Override
    public CommonResult<Boolean> add(ChargeCouponAddReqDto couponAddReqDto) {
        checkCoupon(couponAddReqDto);
        ChargeCoupon record = new ChargeCoupon();
        BeanUtils.copyProperties(couponAddReqDto, record);
        record.setStatus(CouponStatusEnum.UNPUBLISHED.getStatus());
        record.setCreateTime(new Date());
        record.setCode(CouponCodeUtil.couponCode(couponAddReqDto.getCreateUserId()));
        chargeCouponMapper.insert(record);
        redissonClient.getBucket(ChargeMarketRedisKeyPrefix.CHARGE_COUPON_KEY + record.getId()).set(JSON.toJSONString(record));
        return CommonResult.success(Boolean.TRUE);
    }

    private void checkCoupon(ChargeCouponAddReqDto couponAddReqDto) {
        if (StringUtil.isBlank(couponAddReqDto.getName())) {
            throw new BusinessException("优惠券名称不能为空");
        }
        if (couponAddReqDto.getLimit() == null) {
            throw new BusinessException("领取限制不能为空");
        }
        if (couponAddReqDto.getLimit() < 0) {
            throw new BusinessException("领取限制不正确");
        }
        if (couponAddReqDto.getCount() == null) {
            throw new BusinessException("优惠券数量不能为空");
        }
        if (couponAddReqDto.getCount() <= 0) {
            throw new BusinessException("优惠券数量不正确");
        }
        if (couponAddReqDto.getTogether() == null) {
            throw new BusinessException("叠加使用不能为空");
        }
        if (couponAddReqDto.getTogether() != 0 && couponAddReqDto.getTogether() != 1) {
            throw new BusinessException("叠加使用不正确");
        }
        if (couponAddReqDto.getUseType() == null) {
            throw new BusinessException("优惠券使用门槛类型不能为空");
        }
        // TODO: 2024-10-21 还可以进一步校验 比如满减券，满多少，减多少，折扣券，折扣率等等
        if (couponAddReqDto.getSendType() == null) {
            throw new BusinessException("优惠券发放类型不能为空");
        }
        //积分兑换，那就必须设置积分
        if (couponAddReqDto.getSendType() == CouponSendTypeEnum.POINT.getSendType() && couponAddReqDto.getPoint() == null) {
            throw new BusinessException("积分发放类型必须设置积分");
        }
    }

    @Override
    public CommonResult<Boolean> update(ChargeCouponUpdateReqDto couponUpdateReqDto) {
        ChargeCoupon record = new ChargeCoupon();
        BeanUtils.copyProperties(couponUpdateReqDto, record);
        //类型修改后，point置空
        if (couponUpdateReqDto.getSendType() != CouponSendTypeEnum.POINT.getSendType()) {
            if (couponUpdateReqDto.getPoint() != null) {
                record.setPoint(null);
            }
        }
        // TODO: 2024-10-21 其他的条件修改页可以做类似的判断，然后对数据里的属性值进行置空
        chargeCouponMapper.updateByPrimaryKey(record);
        redissonClient.getBucket(ChargeMarketRedisKeyPrefix.CHARGE_COUPON_KEY + record.getId()).set(JSON.toJSONString(record));
        return CommonResult.success(Boolean.TRUE);
    }

    @Override
    public CommonResult<Boolean> cacheStockQuantity(ChargeCouponAddReqDto couponAddReqDto) {
        Integer couponId = couponAddReqDto.getId();
        RBucket<String> bucket = redissonClient.getBucket(ChargeMarketRedisKeyPrefix.CHARGE_COUPON_KEY + couponId);
        ChargeCoupon record = JSON.parseObject(bucket.get(), ChargeCoupon.class);
        if (record != null) {

            RBucket<String> chargeActivityBucket = redissonClient.getBucket(ChargeMarketRedisKeyPrefix.ACTIVITY_CONDITION_INFO_KEY + couponAddReqDto.getId());
            ChargeActivity chargeActivity = JSON.parseObject(chargeActivityBucket.get(), ChargeActivity.class);

            RBucket<String> chargeActivityConditionBucket = redissonClient.getBucket(ChargeMarketRedisKeyPrefix.ACTIVITY_CONDITION_KEY + chargeActivity.getActivityConditionId());
            ChargeActivityCondition chargeActivityCondition = JSON.parseObject(chargeActivityConditionBucket.get(), ChargeActivityCondition.class);

            //抢购标识
            RBucket<Integer> secondKillBucket = redissonClient.getBucket(ChargeMarketRedisKeyPrefix.CHARGE_COUPON_SECONDS_KILL_KEY + record.getId());
            secondKillBucket.set(record.getId());
            //有效期 往后延期一小时
            secondKillBucket.expire(chargeActivityCondition.getEndTime().getTime() - System.currentTimeMillis() + 3600000, TimeUnit.MILLISECONDS);

            RList<Integer> list = redissonClient.getList(ChargeMarketRedisKeyPrefix.CHARGE_COUPON_STOCK_QUANTITY_KEY + couponId);
            // 库存 缓存到redis 中
            for (int i = 0; i < record.getCount(); i++) {
                list.add(couponId);
            }
        }
        return CommonResult.success(Boolean.TRUE);
    }

    @Override
    public CommonResult<BaseRespPageDto<List<ChargeCouponRespDto>>> page(ChargeCouponPageReqDto chargeCouponPageReqDto) {
        int pageNum = chargeCouponPageReqDto.getPageNum() == null ? 1 : chargeCouponPageReqDto.getPageNum();
        int pageSize = chargeCouponPageReqDto.getPageSize() == null ? 10 : chargeCouponPageReqDto.getPageSize();
        Page<ChargeCoupon> page = PageHelper.startPage(pageNum, pageSize);
        List<ChargeCoupon> chargeCouponConditions = chargeCouponMapper.selectPageByCondition(chargeCouponPageReqDto);
        List<ChargeCouponRespDto> chargeCouponConditionRespDtoList = new ArrayList<>();
        for (ChargeCoupon chargeCoupon : chargeCouponConditions) {
            ChargeCouponRespDto chargeCouponRespDto = new ChargeCouponRespDto();
            BeanUtils.copyProperties(chargeCoupon, chargeCouponRespDto);

            int exchangeCodeCount = exchangeCodeMapper.countByCouponId(chargeCoupon.getId());
            chargeCouponRespDto.setExchangeCodeCount(exchangeCodeCount);
            chargeCouponConditionRespDtoList.add(chargeCouponRespDto);
        }
        BaseRespPageDto<List<ChargeCouponRespDto>> baseRespPageDto = new BaseRespPageDto<>();
        baseRespPageDto.setTotalCount(page.getTotal());
        baseRespPageDto.setTotalPage(page.getPages());
        baseRespPageDto.setData(chargeCouponConditionRespDtoList);
        return CommonResult.success(baseRespPageDto);
    }

    @Override
    public CommonResult<BaseRespPageDto<List<ChargeCouponApiRespDto>>> pageApi(ChargeCouponPageReqDto chargeCouponPageReqDto) {
//        ChargeUserLoginResDto user = UserCacheUtil.getUser();
        // TODO: 2024-11-18 只为了测试方便
        ChargeUserLoginResDto user = new ChargeUserLoginResDto();
        user.setId(1L);
        Long userId = user.getId();
        Integer pageNum = chargeCouponPageReqDto.getPageNum();
        if (pageNum == null) {
            pageNum = 1;
        }
        Integer pageSize = chargeCouponPageReqDto.getPageSize();
        if (pageSize == null) {
            pageSize = 10;
        }
        Page<ChargeCoupon> page = PageHelper.startPage(pageNum, pageSize);
        List<ChargeCoupon> chargeCouponConditions = chargeCouponMapper.selectPageByCondition(chargeCouponPageReqDto);
        List<ChargeCouponApiRespDto> chargeCouponConditionRespDtoList = new ArrayList<>();
        for (ChargeCoupon chargeCoupon : chargeCouponConditions) {
            ChargeCouponApiRespDto chargeCouponRespDto = new ChargeCouponApiRespDto();
            BeanUtils.copyProperties(chargeCoupon, chargeCouponRespDto);
            chargeCouponRespDto.setSpecialStr(chargeCoupon.getSpecial() == null || chargeCoupon.getSpecial() == 0 ? "通用券" : "专用券");
            chargeCouponRespDto.setExpireTypeStr(chargeCouponPageReqDto.getExpireType() == 0 ? "" : "已过期");
            String canGetStr = "可领取";
            if (chargeCoupon.getLimit() == 1) {
                long count = redissonClient.getAtomicLong(ChargeMarketRedisKeyPrefix.USER_GET_COUPON_TIMES_KEY + userId + ":" + chargeCoupon.getId()).get();
                canGetStr = count >= chargeCoupon.getLimitTimes() ? "已领取" : canGetStr;
            }
            chargeCouponRespDto.setCanGetStr(canGetStr);

            chargeCouponConditionRespDtoList.add(chargeCouponRespDto);
        }
        BaseRespPageDto<List<ChargeCouponApiRespDto>> baseRespPageDto = new BaseRespPageDto<>();
        baseRespPageDto.setTotalCount(page.getTotal());
        baseRespPageDto.setTotalPage(page.getPages());
        baseRespPageDto.setData(chargeCouponConditionRespDtoList);
        return CommonResult.success(baseRespPageDto);
    }

    @Override
    public CommonResult<Boolean> delete(Integer id) {
        return chargeCouponMapper.deleteByPrimaryKey(id) > 0 ? CommonResult.success(Boolean.TRUE) : CommonResult.failed();
    }


    @Override
    public CommonResult<Boolean> start(Integer id) {
        return chargeCouponMapper.start(id) > 0 ? CommonResult.success(Boolean.TRUE) : CommonResult.failed();
    }

    @Override
    public CommonResult<CheckCouponRespDto> checkCoupon(CheckCouponReqDto checkCouponReqDto) {
        List<Long> userCouponIdList = checkCouponReqDto.getUserCouponIdList();

        BigDecimal orderAmount = checkCouponReqDto.getOrderAmount();

        CheckCouponRespDto checkCouponRespDto = new CheckCouponRespDto();
        ConcurrentHashMap<Long, String> couponCheckResultMap = new ConcurrentHashMap<>();

        CountDownLatch latch = new CountDownLatch(userCouponIdList.size());
        int size = userCouponIdList.size();

        for (Long userCouponId : userCouponIdList) {
            threadPoolTaskExecutor.execute(() -> {
                try {
                    checkResult(userCouponId, size, orderAmount, couponCheckResultMap, latch);
                } catch (Exception e) {
                    log.error("更新优惠券核销结果失败", e);
                    latch.countDown();
                }
            });
        }

        try {
            latch.await(); // 阻塞直到所有任务完成
        } catch (InterruptedException e) {
            log.error("优惠券校验果失败", e);
            return CommonResult.failed("校验失败");
        }
        checkCouponRespDto.setCouponCheckResultMap(couponCheckResultMap);
        return CommonResult.success(checkCouponRespDto);
    }

    private void checkResult(Long userCouponId, int size, BigDecimal orderAmount, ConcurrentHashMap<Long, String> couponCheckResultMap, CountDownLatch latch) {
        //获取用户优惠券记录信息
        UserCouponCacheDto userCouponCacheDto = getUserCouponCacheDto(userCouponId);
        //获取优惠券信息
        CouponCacheDto couponCacheDto = getCouponCacheDto(userCouponCacheDto);

        if (userCouponCacheDto.getPeriod().before(new Date())) {
            couponCheckResultMap.put(userCouponId, couponCacheDto.getName() + "优惠券已过期");
            latch.countDown();
            return;
        }
        //状态不是未使用
        if (userCouponCacheDto.getStatus() != 0) {
            couponCheckResultMap.put(userCouponId, couponCacheDto.getName() + "优惠券已过期");
            latch.countDown();
            return;
        }

        if (couponCacheDto.getTogether() == 1 && size > 1) {
            couponCheckResultMap.put(userCouponId, couponCacheDto.getName() + "不能叠加使用");
        }
        //如果是满减券，则判断订单金额是否满足满减条件
        if (couponCacheDto.getUseType() == CouponUseTypeEnum.FULL_REDUCE.getType()) {
            if (orderAmount.compareTo(couponCacheDto.getFullReduce()) < 0) {
                couponCheckResultMap.put(userCouponId, couponCacheDto.getName() + "优惠券不满足满减条件");
            }
        }
        latch.countDown();
    }

    private CouponCacheDto getCouponCacheDto(UserCouponCacheDto userCouponCacheDto) {
        RBucket<String> couponBucket = redissonClient.getBucket(ChargeMarketRedisKeyPrefix.CHARGE_COUPON_KEY + userCouponCacheDto.getCouponId());
        return JSON.parseObject(couponBucket.get(), CouponCacheDto.class);
    }

    private UserCouponCacheDto getUserCouponCacheDto(Long userCouponId) {
        RBucket<String> userCouponBucket = redissonClient.getBucket(ChargeMarketRedisKeyPrefix.CHARGE_USER_COUPON_KEY + userCouponId);
        return JSON.parseObject(userCouponBucket.get(), UserCouponCacheDto.class);
    }
}